// Ninf-G-XX, ver. 0.1
// Copyright 2011 National Institute of Advanced Industrial Science and Technology
// 
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// 
//     http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/* 
 *  NINF stub main and make file generator 
 *
 *  $Id: GenMain.cpp,v 1.2 2004/03/08 09:34:09 nakada Exp $
 */

#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <stdarg.h>
#define NC_GEN_MAIN
#include "Gen.hpp"

#define FILE_NAME_LEN 	125
char title_file_name[FILE_NAME_LEN];

#ifndef CPP_FILE
#define CPP_FILE "cc -E"
#endif

using namespace ninf::gen;
using namespace std;

static char CPP_OPEN[1000];

/* the number of errors */
int nerrors;

char *program = NULL;	/* this program */

/**  function declarations  **/

extern void init_expr_code_info();
extern void initialize_lex();
extern int yyparse();

static void generateMakefile();
static const char * getRequiredDepends(const char * required);

static void check_nerrors();
static void print_usage();

/*
 *  MAIN
 */

int main(int argc, char *argv[])
{
    program = argv[0];

    --argc;
    ++argv;

    sprintf(CPP_OPEN, "%s ", CPP_FILE);

    /* parse command line */
    while (argc >0 && argv[0][0] == '-') {
      if (strncmp(argv[0], "-debug", 6) == 0){
	ncGenGlobals.debug = 1;
      } else 
      if (strncmp(argv[0], "-gmake", 6) == 0){
	ncGenGlobals.gmake = 1;
      } else 
      if (strncmp(argv[0], "-vanilla", 8) == 0){
	ncGenGlobals.universe = TARGET_UNIVERSE_VANILLA;
      } else 
      if (strncmp(argv[0], "-help", 5) == 0){
	print_usage();
      } 
      --argc;
      ++argv;
    }
    
    if (argc > 0){
      ncGenGlobals.sourceFilename = argv[0];
      strcat(CPP_OPEN, argv[0]);
      if ((ncGenGlobals.sourceFp = popen(CPP_OPEN, "r")) == NULL){
	fprintf(stderr,"cannot open %s\n", ncGenGlobals.sourceFilename);
	exit(1);
      }
      /* set title as default */
      strcpy(title_file_name, ncGenGlobals.sourceFilename);
    } else 
	ncGenGlobals.sourceFp = stdin;

    if (argc > 1){
      ncGenGlobals.outputFilename = argv[1];
      if ((ncGenGlobals.outputFp = fopen(ncGenGlobals.outputFilename,"w")) == NULL){
	fprintf(stderr,"cannot open %s\n", ncGenGlobals.outputFilename);
	exit(1);
      }
    } else 
      ncGenGlobals.outputFp = stdout;

    /* DEBUG */ ncGenGlobals.debugFp = stdout;

    initialize_lex();
    
    /* start processing */
    yyparse();

    if (!nerrors){
      /* generate stub programs */
      for(unsigned int n = 0; n < ncGenGlobals.stubs.size(); n++){
	ncGenGlobals.stubs[n]->generateStubProgram();
	ncGenGlobals.stubs[n]->generateInfoFile();
      }
      /* generate make file */
      generateMakefile();
      
      /* geneate interface database */
    }
    return(nerrors?1:0);
}


void generateMakefile(){
  FILE *fp;
  char fname[1000];

  sprintf(fname,"%s.mak", ncGenGlobals.currentModule);
  
  if((fp = fopen(fname,"w")) == NULL){
    error("%s: cannot create\n",fname);
    return;
  }

  fprintf(fp,"# This file '%s' was created by %s. Don't edit.\n\n",
	  fname, program);
  
  fprintf(fp, "# Default setting\n\n");

  if (ncGenGlobals.universe == TARGET_UNIVERSE_STANDARD)
    fprintf(fp, "CONDOR_COMPILE = %s\n", CONDOR_COMPILE);
  else
    fprintf(fp, "CONDOR_COMPILE = \n");

  fprintf(fp, "CC = %s\n", "g++");
  
  if (ncGenGlobals.compilerName == NULL)
    fprintf(fp, "NINF_COMPILER = $(CC)\n");
  else
    fprintf(fp, "NINF_COMPILER = %s\n", ncGenGlobals.compilerName);
  
  if (ncGenGlobals.linkerName == NULL)
    fprintf(fp, "NINF_LINKER = $(CC)\n\n");
  else
    fprintf(fp, "NINF_LINKER = %s\n\n", ncGenGlobals.linkerName);

  fprintf(fp, "CONDOR_OS = $(shell condor_status -format '%%s\\n' OpSys $(HOSTNAME) | head -1)\n");
  fprintf(fp, "CONDOR_ARC = $(shell condor_status -format '%%s\\n' Arch $(HOSTNAME) | head -1)\n");

  
  fprintf(fp, "# Include template\n\n");
  fprintf(fp, "include $(NC_DIR)/template\n");
  
  fprintf(fp,"\n# CompileOptions:\n\n");
  for(unsigned i = 0; i < ncGenGlobals.compileOptions.size(); i++)
    fprintf(fp,"%s\n",ncGenGlobals.compileOptions[i]);
  
  fprintf(fp,"\n# stub sources\n\nNINF_STUB_SRC =");
  for(unsigned i = 0; i < ncGenGlobals.stubs.size(); i++)
    fprintf(fp," %s.cpp ", 
	    ncGenGlobals.stubs[i]->interface.getStubName().c_str());
  
  fprintf(fp,"\n\n# stub programs\n\nNINF_STUB_PROGRAM =");
  for(unsigned i = 0; i < ncGenGlobals.stubs.size(); i++)
    fprintf(fp," %s.$(CONDOR_OS).$(CONDOR_ARC)", 
	    ncGenGlobals.stubs[i]->interface.getStubName().c_str());
  
  fprintf(fp,"\n\nall: $(NINF_STUB_PROGRAM)\n\n");

  for(unsigned i = 0; i < ncGenGlobals.stubs.size(); i++){
    string stubName = ncGenGlobals.stubs[i]->interface.getStubName();
    char * stubNameC = strdup(stubName.c_str());

    fprintf(fp,"%s.o: %s.cpp\n\t$(NINF_COMPILER) $(CFLAGS) $(NC_CFLAGS)  -c %s.cpp\n",
	    stubNameC, stubNameC, stubNameC);
    fprintf(fp,"%s.$(CONDOR_OS).$(CONDOR_ARC): %s.o %s\n",
	    stubNameC, stubNameC,
	    getRequiredDepends(ncGenGlobals.stubs[i]->required));

    fprintf(fp,"\t$(CONDOR_COMPILE) $(NINF_LINKER) $(CFLAGS)  -o %s.$(CONDOR_OS).$(CONDOR_ARC)"
	    " %s.o $(LDFLAGS) $(NC_STUB_LDFLAGS) %s $(LIBS)",
	    stubNameC, stubNameC,
	    (ncGenGlobals.stubs[i]->required == NULL) ? "": 
	    ncGenGlobals.stubs[i]->required);
    for(unsigned j = 0; j < ncGenGlobals.libs.size(); j++)
      fprintf(fp," %s", ncGenGlobals.libs[j]);
    
    fprintf(fp,"\n\n");
    free(stubNameC);
  }
  
  fprintf(fp,"\nclean:\n");
  for(unsigned i = 0; i < ncGenGlobals.stubs.size(); i++){
    string stubName = ncGenGlobals.stubs[i]->interface.getStubName();
    const char * stubNameC = stubName.c_str();
    fprintf(fp, "\trm -f %s.o %s.cpp\n",
	    stubNameC, stubNameC);
  }  
  fprintf(fp, "\nveryclean: clean\n");
  fprintf(fp, "\trm -f $(NINF_STUB_PROGRAM)\n");

  for(unsigned i = 0; i < ncGenGlobals.stubs.size(); i++){
    if (ncGenGlobals.stubs[i]->required != NULL) {
      fprintf(fp,"\trm -f ");
      fprintf(fp,"%s\n", ncGenGlobals.stubs[i]->required);
    }
  }
  fprintf(fp, "\n");

  fprintf(fp,"\n# END OF Makefile\n");
  
  fclose(fp);
}


static const char * getRequiredDepends(const char * required){
  const char * NULL_STR = "";
  char * depend, *tmp_required;
  char * tmp;
  if (required == NULL)
    return NULL_STR;
  /** malloc depend */

  depend = (char *)calloc(1, strlen(required) + 1);
  tmp_required = strdup(required);

  if (tmp_required == NULL || depend == NULL)
    return NULL_STR;
  depend[0] = '\0';
  
  tmp = strtok(tmp_required, "\t ");
  if (tmp != NULL) {
    if (tmp[0] != '-')
      strcat(depend, tmp);
    tmp = strtok(NULL, "\t ");
  }
  while (tmp != NULL){
    if (tmp[0] != '-') {
      strcat(depend, " ");
      strcat(depend, tmp);
    }
    tmp = strtok(NULL, "\t ");
  }
  free(tmp_required);
  return (const char *) depend;
}

/* 
 * error messages 
 */

void where(int lineno)
{ 
    /* print location of error  */
    fprintf(stderr, "\"%s\", line %d: ",title_file_name, lineno);
}

/* nonfatal error message */
/* VARARGS0 */
void ninf::gen::error(const char *fmt, ...) 
{ 
    va_list args;

    ++nerrors;
    where(ncGenGlobals.lineno); /*, "Error");*/
    va_start(args, fmt);
    vfprintf(stderr, fmt, args);
    va_end(args);
    fprintf(stderr, "\n" );
    fflush(stderr);
    check_nerrors();
}

/* VARARGS0 */
void ninf::gen::error_at_node(expr x, const char *fmt, ...) 
{ 
    va_list args;
    ++nerrors;
    va_start(args, fmt);
    where((int) x->lineno); /* , "ErrorAtNode"); */
    vfprintf(stderr, fmt, args);
    va_end(args);
    fprintf(stderr, "\n" );
    fflush(stderr);
    if(nerrors > 30){
      /* give the compiler the benefit of the doubt */
      fprintf(stderr, 
	      "too many error, cannot recover from earlier errors: goodbye!\n" );
      exit(1);
    }
}

/* VARARGS0 */
void warning_at_node(expr x, const char *fmt, ...) 
{ 
  va_list args;

  where(x->lineno); /*, "WarnAtNode"); */
  fprintf(stderr,"warning:");
  va_start(args, fmt);
  vfprintf(stderr, fmt, args);
  va_end(args);
  fprintf(stderr, "\n" );
  fflush(stderr);
}

void
check_nerrors()
{
    if(nerrors > 30){
      /* give the compiler the benefit of the doubt */
      fprintf(stderr, 
	      "too many error, cannot recover from earlier errors: goodbye!\n" );
      exit(1);
    }
}

/* compiler error: die */
/* VARARGS1 */
void
ninf::gen::fatal(const char *fmt, ...)
{ 
    va_list args;
    
    where(ncGenGlobals.lineno); /*, "Fatal");*/
    fprintf(stderr, "compiler error: " );
    va_start(args, fmt);
    vfprintf(stderr, fmt, args);
    va_end(args);
    fprintf(stderr, "\n" );
    fflush(stderr);
    abort();
}

int warning_flag = FALSE; 

/* warning */
void
warning(const char *fmt, ...) 
{  
  va_list args;
  
  if(warning_flag) return;
  where(ncGenGlobals.lineno); /*, "Warn");*/
  va_start(args, fmt);
  fprintf(stderr, "warning: " );
  vfprintf(stderr, fmt, args);
  va_end(args);
  fprintf(stderr, "\n" );
  fflush(stderr);
}


/*
 *   Print usage
 */

void
print_usage()
{
    puts("usage: ninf_gen [-debug] [-gmake] [-help] [-vanilla] [input_file] [output_file]");
    puts("\t-debug   Debug option");
    puts("\t-gmake   Produce makefile in gnu make style");
    puts("\t-vanilla Produce makefile for vanilla universe");
    puts("\t-help    Show this message");
}
